package com.realshare.bby.service.integral;

import com.realshare.bby.model.*;
import com.realshare.frame.util.DateUtils;
import com.realshare.frame.util.EasyHashMap;
import org.apache.commons.lang3.StringUtils;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.SQLReady;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

/**
 * ${DESCRIPTION}
 * author leisure
 * email 15075107120@139.com
 * create 2019-02-21 上午 11:17
 * modify 2019-02-21 上午 11:17
 **/
@Service
public class IntegralCalculate {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SQLManager sqlManager;

    /**
     * 计算每个用户的发生业务后的第一次计算积分，当规则没有配置用户组的时候则为全部用户给积分，不需要补算
     *
     * @param type        0.计算正常用户组用户积分；1.补算用户组核验失败的用户被处理之后积分
     * @param ruleConfigs 规则列表
     * @return 是否计算成功
     */
    String calculateBusinessIntegral(int type, List<RuleConfigs> ruleConfigs) {
        String msg = type == 0 ? "计算正常用户组用户积分" : "补算用户组核验失败的用户被处理之后积分";
        log.info("开始计算每个用户的发生业务后的第一次计算积分【" + msg + "】");
        try {
            String now = DateUtils.getNowDateTime();

            String updateSql = "update RULE_GROUP_MEMBERS set START_INTEGRAL_TIME=?,NEED_PATCH=0 " +
                    "where GROUP_ID=? and STATUS='1' and  NEED_PATCH='%s'";
            updateSql = String.format(updateSql, type == 0 ? "0" : "1");
            SQLReady sqlReady = new SQLReady(updateSql);

            //循环处理现有规则
            for (RuleConfigs configs : ruleConfigs) {
                if (configs.getRuleGroups().size() <= 0 && type == 1) {
                    //如果规则没有涉及到用户组，则不会出现用户组核验失败补算积分的情况，不在处理
                    log.info("取消计算，因规则没有配置用户组的时候则为全部用户给积分，不需要补算【" + msg + "】");
                    return "";
                }
                //处理最后一次计算时间，补算数据时或者第一次计算时为配置起始时间
                if (StringUtils.isEmpty(configs.getLastDataTime()) || type == 1) {
                    configs.setLastDataTime(configs.getStartTime());
                }
                //得到业务模型的sql
                String sql = "select t.*,${idNo} ID_NO,${bankNo} ORG_CODE from (" + configs.getRuleDataModels().getExtractSql() + ") t where 1=1";
                //根据适用网点筛选需要给积分的用户，如果是空，则为所有网点适用
                String bankNos = configs.getBankNos();
                if (StringUtils.isNotEmpty(bankNos)) {
                    //处理网点字符串，以适用条件查询
                    bankNos = (bankNos.indexOf(",") == 0 ? bankNos.substring(1) : bankNos).replaceAll(",", "','");
                    //包装sql，根据适用网点筛选需要给积分的用户
                    sql += String.format(" and ${bankNo}  in('%s')", bankNos);
                }
                //处理配置中的补充条件
                if (StringUtils.isNotEmpty(configs.getConfineSql())) {
                    sql += String.format(" %s ", configs.getConfineSql());
                }
                //循环用户组，根据用户组用户筛选需给积分的用户
                StringBuilder groupWhereSql = new StringBuilder();
                for (int i = 0; i < configs.getRuleGroups().size(); i++) {
                    String gName;
                    RuleGroups groups = configs.getRuleGroups().get(i);
                    //设置算分参数（积分标记类型）：1.个人证件（身份证、军官证）；2.客户号；3.外部账号（银行卡号、存折号、存单号）；4.内部账号（流水号）；
                    switch (groups.getIdentType()) {
                        case 1://个人证件（身份证、军官证）
                            gName = "${identityCode}";
                            break;
                        case 2://客户号
                            gName = "${customerNo}";
                            break;
                        case 3://外部账号（银行卡号、存折号、存单号）
                            gName = "${externalNo}";
                            break;
                        case 4://内部账号（流水号
                            gName = "${internalNo}";
                            break;
                        default:
                            return "用户组【" + groups.getName() + "】中未发现任何用户标记";
                    }
                    gName = i == 0 ? gName : " or " + gName;
                    String groupSql = " %s in (select ID_NO from RULE_GROUP_MEMBERS " +
                            "where GROUP_ID='%s' and STATUS='1' and  NEED_PATCH='%s' group by ID_NO)  ";
                    groupWhereSql.append(String.format(groupSql, gName, groups.getId(), type == 0 ? "0" : "1"));
                    //更新用户开始给分时间
                    sqlReady.setArgs(new String[]{now, groups.getId()});
                }
                sql = groupWhereSql.length() > 0 ? sql + " and (" + groupWhereSql + ") " : sql;
                //处理赠分
                handlerGiveIntegral(configs, sql, type == 0 ? "正常-" : "补算-");
                //处理扣分
                handlerDeductIntegral(configs, sql, type == 0 ? "正常-" : "补算-");
            }
            if (sqlReady.getArgs().length > 0) {
                sqlManager.executeUpdate(sqlReady);
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
            return "计算每个用户的发生业务后的第一次计算积分时出错，错误信息为：【" + e.getMessage() + "】";
        } finally {
            log.info("结束计算每个用户的发生业务后的第一次计算积分【" + msg + "】");
        }
    }

    /**
     * 处理赠分数据
     */
    private void handlerGiveIntegral(RuleConfigs configs, String sql, String str) {
        log.info(str + "开始执行积分规则【" + configs.getName() + "】的赠分数据任务");
        int index = 0;
        //保存积分计算任务sql
        String saveSql = "insert into RULE_INTEGRAL_TASK(ID,RULE_CONFIG_ID,RULE_CONFIG_ACTION_ID,INTEGRAL" +
                ",INTEGRAL_TYPE,DATA_TIME,GIVE_TIME,CLAIM_LIMIT_TIME,ID_NO,CREATOR,CREATE_TIME,STATUS" +
                ",HANDLER_STATUS,IDENTITY_CODE,ORG_CODE,MONEY,FIRST_GIVE) " +
                "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        SQLReady sqlReady = new SQLReady(saveSql);

        for (RuleConfigGive give : configs.getGives()) {
            index++;
            //保留5位小数，只舍
            String querySql = String.format("select t.*,TRUNCATE((%s),5) INTEGRAL from (%s) t", give.getPointValue(), sql);
            //替换sql参数
            querySql = replaceParam(configs.getRuleDataModels(), querySql, configs.getLastDataTime());
               /*
               关闭，原因为如果按日每天给分的时候，缺少几天没跑任务时就会正常重复 modify by caochuanxi 181228
               //判断是否有重复数据
                judgeRepeatData(query, querySql, index, configs, "赠分");
                */

            log.info(str + "积分规则【" + configs.getName() + "】【" + configs.getId() + "】的第【" + index + "】个赠分动作的sql为：" + querySql);
            List<EasyHashMap> mapList = sqlManager.execute(new SQLReady(querySql), EasyHashMap.class);
            String now = DateUtils.getNowDateTime();
            for (EasyHashMap map : mapList) {
                //实际业务数据时间
                String dataTime = map.getString(configs.getDataTimeMetas().getName());
                //实际业务交易金额
                String tradeAmount = "0";
                RuleDataModelMetas tradeAmountMetas = configs.getTradeAmountMetas();
                if (tradeAmountMetas != null) {
                    tradeAmount = map.getString(tradeAmountMetas.getName());
                }

                //每次需要给的积分保留5位小数，只舍
                double integral = map.getDouble("INTEGRAL");
                //积分标记证件号码
                String idNo = map.getString("ID_NO");
                //积分标记网点编号
                String orgCode = map.getString("ORG_CODE");
                //保存每期积分数据

                //判断需要给多少期积分
                int cycle;
                if (give.getActionNum() > 0) {
                    //固定周期赠送积分
                    cycle = give.getActionNum().intValue();
                } else {
                    //按截止日期赠送积分
                    String dueDate = replaceParam(configs.getRuleDataModels(), give.getDueDate(), configs.getLastDataTime());
                    dueDate = map.getString(dueDate);
                    if ("DAY".equals(give.getIntervalKind())) {
                        cycle = DateUtils.getDayDiff(DateUtils.parse(dueDate), DateUtils.parse(dataTime));

                    } else if ("MONTH".equals(give.getIntervalKind())) {
                        cycle = DateUtils.getMonthDiff(DateUtils.parse(dueDate), DateUtils.parse(dataTime));

                    } else {
                        cycle = DateUtils.getYearDiff(DateUtils.parse(dueDate), DateUtils.parse(dataTime));

                    }
                }
                //处理每期给分数据
                Date giveTimeDate = DateUtils.parse(dataTime);
                for (int i = 0; i < cycle; i++) {
                    List<String> args = new LinkedList<String>();
                    Date nextGiveTime;
                    if ("DAY".equals(give.getIntervalKind())) {
                        if (i == 0 && give.getPrepay() == 0) {//期末后赠分
                            giveTimeDate = DateUtils.addDay(giveTimeDate, 1);
                        }
                        nextGiveTime = DateUtils.addDay(giveTimeDate, give.getIntervalCount().intValue());

                    } else if ("MONTH".equals(give.getIntervalKind())) {
                        if (i == 0 && give.getPrepay() == 0) {//期末后赠分
                            giveTimeDate = DateUtils.addMonth(giveTimeDate, 1);
                        }
                        nextGiveTime = DateUtils.addMonth(giveTimeDate, give.getIntervalCount().intValue());

                    } else {
                        if (i == 0 && give.getPrepay() == 0) {//期末后赠分
                            giveTimeDate = DateUtils.addYear(giveTimeDate, 1);
                        }
                        nextGiveTime = DateUtils.addYear(giveTimeDate,give.getIntervalCount().intValue());

                    }
                    //用户领取积分限制截止时间
                    Date d = DateUtils.addDay(giveTimeDate, configs.getClaimDay().intValue());
                    String claimLimitTime = DateUtils.formatLongDate(d);

                    args.add(0, UUID.randomUUID().toString());
                    args.add(1, configs.getId());
                    args.add(2, give.getId());
                    args.add(3, integral + "");
                    //积分类型：1.积分规则赠分；2.手工积分补贴；3.不扣分但停止给分；4.扣除指定分；5.扣除期前预赠送积分；6.扣除已赠送所有积分；
                    args.add(4, "1");
                    //实际业务数据时间
                    args.add(5, dataTime);
                    args.add(6, DateUtils.formatLongDate(giveTimeDate));
                    args.add(7, claimLimitTime);
                    args.add(8, idNo);
                    args.add(9, "system");
                    args.add(10, now);
                    //状态：0.扣分后无效的赠分；1.待领积分；2.待赠积分；3.已赠积分；4.过期积分；5.待扣积分；6.已扣积分；11.待审核积分；12.审核未通过积分；
                    args.add(11, configs.getClaimDay() == 0 ? "2" : "1");
                    //处理状态：0.跑分服务程序未处理；1.正常（已处理）
                    args.add(12, "0");
                    args.add(13, "0");
                    args.add(14, orgCode);
                    args.add(15, tradeAmount);
                    args.add(16, i == 0 ? "1" : "0");
                    sqlReady.setArgs(args.toArray());
                    sqlManager.executeUpdate(sqlReady);
                    giveTimeDate = nextGiveTime;
                }
            }
        }
        log.info(str + "结束执行积分规则【" + configs.getName() + "】【" + configs.getId() + "】的赠分数据任务");
    }

    /**
     * 处理扣分数据
     */
    private void handlerDeductIntegral(RuleConfigs configs, String sql, String str) {
        log.info(str + "开始执行积分规则【" + configs.getName() + "】【" + configs.getId() + "】的扣分数据任务");
        int index = 0;
        //保存积分计算任务sql
        String saveSql = "insert into RULE_INTEGRAL_TASK(ID,RULE_CONFIG_ID,RULE_CONFIG_ACTION_ID,INTEGRAL" +
                ",INTEGRAL_TYPE,DATA_TIME,GIVE_DATA_TIME,ID_NO,CREATOR,CREATE_TIME,STATUS,HANDLER_STATUS" +
                ",ALLOW_NEGATIVE_INTEGRA,IDENTITY_CODE,GIVE_TIME,CLAIM_LIMIT_TIME,RESIDUE_RATIO,ORG_CODE,MONEY,FIRST_GIVE) " +
                "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        SQLReady sqlReady = new SQLReady(saveSql);

        for (RuleConfigDeduct deduct : configs.getDeducts()) {
            index++;
            //保留5位小数，只舍
            String querySql = String.format("select t.* from (%s) t", sql);
            //替换sql参数
            querySql = replaceParam(configs.getRuleDataModels(), querySql, configs.getLastDataTime());

               /*
               关闭，原因为如果按日每天给分的时候，缺少几天没跑任务时就会正常重复 modify by caochuanxi 181228
               //判断是否有重复数据
                judgeRepeatData(query, querySql, index, configs, "扣分");
                */
            log.info(str + "积分规则【" + configs.getName() + "】【" + configs.getId() + "】的第【" + index + "】个扣分动作的sql为：" + querySql);
            List<EasyHashMap> mapList = sqlManager.execute(new SQLReady(querySql), EasyHashMap.class);
            String now = DateUtils.getNowDateTime();
            for (EasyHashMap map : mapList) {
                List<String> args = new LinkedList<String>();
                double integral = 0;//需要扣分的总分数，保留5位小数，只舍
                //积分类型：1.积分规则赠分；2.手工积分补贴；3.不扣分但停止给分；4.扣除指定分；5.扣除期前预赠送积分；6.扣除已赠送所有积分；
                int integralType;
                if (deduct.getDeductKind() == 0) {//不扣分但停止给分
                    integral = 0;
                    integralType = 3;
                } else if (deduct.getDeductKind() == 1) {//扣除指定分
                    integral = deduct.getDeductCount();
                    integralType = 4;
                } else if (deduct.getDeductKind() == 2) {//扣除期前预赠送积分
                    integralType = 5;
                } else {//扣除已赠送所有积分
                    integralType = 6;
                }
                //实际业务数据时间
                String dataTime = map.getString(configs.getDataTimeMetas().getName());
                //实际业务交易金额
                String tradeAmount = "0";
                RuleDataModelMetas tradeAmountMetas = configs.getTradeAmountMetas();
                if (tradeAmountMetas != null) {
                    tradeAmount = map.getString(tradeAmountMetas.getName());
                }
                //实际给分时的数据时间
                String giveDataTime = map.getString(configs.getGiveDataTimeMetas().getName());
                //积分标记证件号码
                String idNo = map.getString("ID_NO");
                //积分标记网点编号
                String orgCode = map.getString("ORG_CODE");
                //实际剩余给分比例
                String residueRatio = map.getString(configs.getResidueRatioIdMetas().getName());
                //保存每期积分数据
                args.add(0, UUID.randomUUID().toString());
                args.add(1, configs.getId());
                args.add(2, deduct.getId());
                args.add(3, integral + "");
                args.add(4, integralType + "");
                args.add(5, dataTime);
                args.add(6, giveDataTime);
                args.add(7, idNo);
                args.add(8, "system");
                args.add(9, now);
                //状态：0.扣分后无效的赠分；1.待领积分；2.待赠积分；3.已赠积分；4.过期积分；5.待扣积分；6.已扣积分；11.待审核积分；12.审核未通过积分；
                args.add(10, "5");
                //处理状态：0.跑分服务程序未处理；1.正常（已处理）
                args.add(11, "0");
                //允许积分余额为负：1.允许；2.不允许；
                args.add(12, deduct.getAllowNegative() + "");
                args.add(13, "0");
                String time = DateUtils.formatLongDate(DateUtils.parse(dataTime));
                args.add(14, time);
                args.add(15, time);
                args.add(16, residueRatio);
                args.add(17, orgCode);
                args.add(18, tradeAmount);
                args.add(19, "1");
                sqlReady.setArgs(args.toArray());
                sqlManager.executeUpdate(sqlReady);
            }
        }
        log.info(str + "结束执行积分规则【" + configs.getName() + "】【" + configs.getId() + "】的扣分数据任务");
    }

    /**
     * 判断是否有重复数据
     */
    private void judgeRepeatData(String querySql, int index, RuleConfigs configs, String action) throws SQLException {
        String judgeSql = "SELECT ID_NO,TOTAL FROM ( " +
                "SELECT ID_NO,COUNT(0) TOTAL FROM (" + querySql + ") T GROUP BY ID_NO) T " +
                "WHERE TOTAL > 1 limit 1";
        log.info("判断是否有重复数据");
        //根据唯一标记判断查询结果是否有重复记录，有重复停止计算
        List<EasyHashMap> mapList = sqlManager.execute(new SQLReady(querySql), EasyHashMap.class);
        if (mapList.size() > 0) {
            EasyHashMap map = mapList.get(0);
            if (map != null && map.getInt("TOTAL") > 0) {
                int total = map.getInt("TOTAL");
                if (total > 0) {
                    String idNo = map.getString("ID_NO");
                    String msg = "处理积分规则【%s】的第【%s】个%s动作时出错，" +
                            "错误信息为：业务模型【%s】中的sql查询结果中根据唯一业务标记发现【%s】条重复数据，重复号码为【%s】";
                    throw new SQLException(String.format(msg, configs.getName(), index, action, configs.getRuleDataModels().getName(), total, idNo));
                }
            }
        }
    }

    /**
     * 替换sql参数
     */
    private String replaceParam(RuleDataModels models, String sql, String lastDataTime) {
        for (RuleDataModelMetas metas : models.getMetas()) {
            //替换中文补充条件
            sql = sql.replaceAll("\\$\\{" + metas.getFieldLabel().toUpperCase() + "}", metas.getName().toUpperCase());
            //替换算分参数（积分标记类型）：1.个人证件（身份证、军官证）；2.客户号；3.外部账号（银行卡号、存折号、存单号）；4.内部账号（流水号）；
            switch (metas.getFieldSign()) {
                case 1://个人证件（身份证、军官证）
                    sql = sql.replaceAll("\\$\\{identityCode}", metas.getName());
                    break;
                case 2://客户号
                    sql = sql.replaceAll("\\$\\{customerNo}", metas.getName());
                    break;
                case 3://外部账号（银行卡号、存折号、存单号）
                    sql = sql.replaceAll("\\$\\{externalNo}", metas.getName());
                    break;
                case 4://内部账号（流水号）
                    sql = sql.replaceAll("\\$\\{internalNo}", metas.getName());
                    break;
                case 10://网点号
                    sql = sql.replaceAll("\\$\\{bankNo}", metas.getName());
                    break;
                default:
                    break;
            }
            //替换积分标记证件号码
            if (metas.getPointKey() == 1) {
                sql = sql.replaceAll("\\$\\{idNo}", metas.getName());
            }
        }
        lastDataTime = DateUtils.format(DateUtils.parse(lastDataTime), "yyyyMMddHHmmss");
        //替换数据时间参数
        sql = sql.replaceAll("\\$\\{最大数据时间}", lastDataTime);
        //替换当前系统时间
        sql = sql.replaceAll("\\$\\{当前系统时间}", DateUtils.format(new Date(), "yyyyMMddHHmmss"));
        return sql;
    }
}
